<?php

require_once 'Vega/Cache/Engine/Abstract.php';

class Vega_Cache_Engine_File extends Vega_Cache_Engine_Abstract
{    
    
    public function __construct($options)
    {
        if (!isset($options['cache_dir']) || empty($options['cache_dir']))
        {
            require_once 'Vega/Cache/Engine/Exception.php';
            throw new Vega_Cache_Engine_Exception('A cache_dir must be specified');
        }
        
        if (!isset($options['lifetime']) || empty($options['lifetime']))
        {
            require_once 'Vega/Cache/Engine/Exception.php';
            throw new Vega_Cache_Engine_Exception('A lifetime must be specified');
        }
        
        $this->_options = $options;
    }
    
    
    /**
     * Saves
     *
     * @param mixed $name
     * @param mixed $data
     * @param integer $lifetime
     * @return boolean
     */
    public function save($name, $data, $lifetime = null)
    {
        $metadata = array('serialized' => false);
        
        $filepath = $this->getFilePath($name);
        
        $fp = @fopen($filepath, 'w', false);
        
        // if can't open file
        if (!$fp) {
            return false;
        }
        
        // lock the file
        flock($fp, LOCK_EX);
        
        // we serialize it if it's not a scalar
        if (!is_scalar($data))
        {
            $data = serialize($data);
            $metadata['serialized'] = true;
        }
        
        // write to the drive
        fwrite($fp, $data);
        flock($fp, LOCK_UN);
        fclose($fp);
        
        // get the default lifetime
        if (null == $lifetime)
        {
            $lifetime = $this->_options['lifetime'];
        }
        $metadata['lifetime'] = $lifetime;
        
        // meta data path
        $metadataPath = $this->getMetadataFilePath($name);
        $fp = @fopen($metadataPath, 'w', false);
        // if can't open file
        if (!$fp) {
            return false;
        }
        
        flock($fp, LOCK_EX);
        fwrite($fp, serialize($metadata));
        flock($fp, LOCK_UN);
        fclose($fp);
        return true;
    }
    
    /**
     * Loads 
     *
     * @param mixed $name
     * @return mixed
     */
    public function load($name)
    {
        // load metadata
        $metadata = $this->loadMetadata($name);
        
        if (!$metadata)
        {
            return false;
        }

        if ($this->isExpired($name, $metadata['lifetime']))
        {
            $this->delete($name);
            return false;
        }

        $filepath = $this->getFilePath($name);

        if (!file_exists($filepath))
        {
            return false;
        }

        $fp = fopen($filepath, 'r', false);
        
        if (!$fp)
        {
            return false;
        }
        
        flock($fp, LOCK_SH);
        $lenght = filesize($filepath);
        $data   = fread($fp, $lenght);
        flock($fp, LOCK_UN);
        fclose($fp);
        
        if ($metadata['serialized'])
        {
            $data = unserialize($data);
        }
        
        return $data;
        
    }

    /**
     * Loads cache metadata
     *
     * @param string $name
     * @return array
     */
    public function loadMetadata($name)
    {
        // first we load the metadata
        $filepath = $this->getMetadataFilePath($name);

        if (!file_exists($filepath))
        {
            return false;
        }

        $fp = @fopen($filepath, 'r', false);

        if (!$fp)
        {
            return false;
        }

        // shared lock
        flock($fp, LOCK_SH);

        $lenght     = filesize($filepath);
        $metadata   = fread($fp, $lenght);

        flock($fp, LOCK_UN);
        fclose($fp);
        // get the metadata
        $metadata = unserialize($metadata);
        return $metadata;
    }

    /**
     * Detele cache file
     *
     * @param mixed $name
     */
    public function delete($name)
    {
        $filepath = $this->getFilePath($name);
        @unlink($filepath);
        
        $metadataFilepath = $this->getMetadataFilePath($name);
        @unlink($metadataFilepath);
        return true;
    }
    
    /**
     * Returns the cache file path
     *
     * @param mixed $name
     * @return string
     */
    public function getFilePath($name)
    { 
        if (isset($this->_options['index_level']) && is_int($this->_options['index_level']))
        {
            $path = $this->_getIndexDir($name, $this->_options['index_level']);
        }
        else
        {
            $path = $this->_options['cache_dir'];
        }
        
        $id = $this->getId($name);
        
        return $path . DIRECTORY_SEPARATOR . $id;
    }
    
    /**
     * Get the 'real' cache directory if in index mode
     *
     * @param string $name
     * @param integer $level
     * @return string
     */
    protected function _getIndexDir($name, $level)
    {
        $curDir = $this->_options['cache_dir'];
        
        for ($i = 0; $i < $level; $i++)
        {
            if (isset($name[$i]))
            {
                $curDir = $curDir . DIRECTORY_SEPARATOR . $name[$i];
                
                if (!file_exists($curDir))
                {
                    @mkdir($curDir);
                }
            }
        }
        return $curDir;
    }
    
    /**
     * Returns metadata file path
     *
     * @param mixed $name
     */
    public function getMetadataFilePath($name)
    {
        if (isset($this->_options['index_level']) && is_int($this->_options['index_level']))
        {
            $path = $this->_getIndexDir($name, $this->_options['index_level']);
        }
        else
        {
            $path = $this->_options['cache_dir'];
        }
        
        $id = $this->getId($name);
        
        return $path . DIRECTORY_SEPARATOR . 'metadata_' . $id;
    }
    
    public function getId($name)
    {
        $id = parent::getId($name);
        $id = str_replace('..', '_', $id);
        $id = str_replace(':', '_', $id);
        return $id;
    }
    
    /**
     * Return true if the cache file is expired
     *
     * @param mixed $name
     * @param integer $lifetime
     * @return boolean
     */
    public function isExpired($name, $lifetime = null)
    {
        $filepath = $this->getFilePath($name);

        // if file does not exist, it's expired
        if (!file_exists($filepath))
        {
            return true;
        }

        if (null === $lifetime)
        {
            $metadata = $this->loadMetadata($name);
            $lifetime = $metadata['lifetime'];
            unset($metadata);
        }

        $expireTime = filemtime($filepath) + $lifetime;
        if (time() > $expireTime) {
            return true;
        }
        return false;
    }
}


